<!-- HTML header for doxygen 1.8.6-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen 1.8.13"/>
<title>OpenCV: Conversion of PyTorch Segmentation Models and Launch with OpenCV</title>
<link href="../../opencv.ico" rel="shortcut icon" type="image/x-icon" />
<link href="../../tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="../../jquery.js"></script>
<script type="text/javascript" src="../../dynsections.js"></script>
<script type="text/javascript" src="../../tutorial-utils.js"></script>
<link href="../../search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="../../search/searchdata.js"></script>
<script type="text/javascript" src="../../search/search.js"></script>
<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js", "TeX/AMSmath.js", "TeX/AMSsymbols.js"],
    jax: ["input/TeX","output/HTML-CSS"],
});
//<![CDATA[
MathJax.Hub.Config(
{
  TeX: {
      Macros: {
          matTT: [ "\\[ \\left|\\begin{array}{ccc} #1 & #2 & #3\\\\ #4 & #5 & #6\\\\ #7 & #8 & #9 \\end{array}\\right| \\]", 9],
          fork: ["\\left\\{ \\begin{array}{l l} #1 & \\mbox{#2}\\\\ #3 & \\mbox{#4}\\\\ \\end{array} \\right.", 4],
          forkthree: ["\\left\\{ \\begin{array}{l l} #1 & \\mbox{#2}\\\\ #3 & \\mbox{#4}\\\\ #5 & \\mbox{#6}\\\\ \\end{array} \\right.", 6],
          forkfour: ["\\left\\{ \\begin{array}{l l} #1 & \\mbox{#2}\\\\ #3 & \\mbox{#4}\\\\ #5 & \\mbox{#6}\\\\ #7 & \\mbox{#8}\\\\ \\end{array} \\right.", 8],
          vecthree: ["\\begin{bmatrix} #1\\\\ #2\\\\ #3 \\end{bmatrix}", 3],
          vecthreethree: ["\\begin{bmatrix} #1 & #2 & #3\\\\ #4 & #5 & #6\\\\ #7 & #8 & #9 \\end{bmatrix}", 9],
          cameramatrix: ["#1 = \\begin{bmatrix} f_x & 0 & c_x\\\\ 0 & f_y & c_y\\\\ 0 & 0 & 1 \\end{bmatrix}", 1],
          distcoeffs: ["(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6 [, s_1, s_2, s_3, s_4[, \\tau_x, \\tau_y]]]]) \\text{ of 4, 5, 8, 12 or 14 elements}"],
          distcoeffsfisheye: ["(k_1, k_2, k_3, k_4)"],
          hdotsfor: ["\\dots", 1],
          mathbbm: ["\\mathbb{#1}", 1],
          bordermatrix: ["\\matrix{#1}", 1]
      }
  }
}
);
//]]>
</script><script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js"></script>
<link href="../../doxygen.css" rel="stylesheet" type="text/css" />
<link href="../../stylesheet.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<!--#include virtual="/google-search.html"-->
<table cellspacing="0" cellpadding="0">
 <tbody>
 <tr style="height: 56px;">
  <td id="projectlogo"><img alt="Logo" src="../../opencv-logo-small.png"/></td>
  <td style="padding-left: 0.5em;">
   <div id="projectname">OpenCV
   &#160;<span id="projectnumber">4.5.2</span>
   </div>
   <div id="projectbrief">Open Source Computer Vision</div>
  </td>
 </tr>
 </tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.8.13 -->
<script type="text/javascript">
var searchBox = new SearchBox("searchBox", "../../search",false,'Search');
</script>
<script type="text/javascript" src="../../menudata.js"></script>
<script type="text/javascript" src="../../menu.js"></script>
<script type="text/javascript">
$(function() {
  initMenu('../../',true,false,'search.php','Search');
  $(document).ready(function() { init_search(); });
});
</script>
<div id="main-nav"></div>
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
     onmouseover="return searchBox.OnSearchSelectShow()"
     onmouseout="return searchBox.OnSearchSelectHide()"
     onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>

<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<iframe src="javascript:void(0)" frameborder="0" 
        name="MSearchResults" id="MSearchResults">
</iframe>
</div>

<div id="nav-path" class="navpath">
  <ul>
<li class="navelem"><a class="el" href="../../d9/df8/tutorial_root.html">OpenCV Tutorials</a></li><li class="navelem"><a class="el" href="../../d2/d58/tutorial_table_of_content_dnn.html">Deep Neural Networks (dnn module)</a></li>  </ul>
</div>
</div><!-- top -->
<div class="header">
  <div class="headertitle">
<div class="title">Conversion of PyTorch Segmentation Models and Launch with OpenCV </div>  </div>
</div><!--header-->
<div class="contents">
<div class="textblock"><h2>Goals</h2>
<p>In this tutorial you will learn how to:</p><ul>
<li>convert PyTorch segmentation models</li>
<li>run converted PyTorch model with OpenCV</li>
<li>obtain an evaluation of the PyTorch and OpenCV DNN models</li>
</ul>
<p>We will explore the above-listed points by the example of the FCN ResNet-50 architecture.</p>
<h2>Introduction</h2>
<p>The key points involved in the transition pipeline of the <a href="https://link_to_cls_tutorial">PyTorch classification</a> and segmentation models with OpenCV API are equal. The first step is model transferring into <a href="https://onnx.ai/about.html">ONNX</a> format with PyTorch <a href="https://pytorch.org/docs/stable/onnx.html#torch.onnx.export"><code>torch.onnx.export</code></a> built-in function. Further the obtained <code>.onnx</code> model is passed into <a class="el" href="../../d6/d0f/group__dnn.html#ga7faea56041d10c71dbbd6746ca854197" title="Reads a network model ONNX. ">cv.dnn.readNetFromONNX</a>, which returns <a class="el" href="../../db/d30/classcv_1_1dnn_1_1Net.html" title="This class allows to create and manipulate comprehensive artificial neural networks. ">cv.dnn.Net</a> object ready for DNN manipulations.</p>
<h2>Practice</h2>
<p>In this part we are going to cover the following points:</p><ol type="1">
<li>create a segmentation model conversion pipeline and provide the inference</li>
<li>evaluate and test segmentation models</li>
</ol>
<p>If you'd like merely to run evaluation or test model pipelines, the "Model Conversion Pipeline" part can be skipped.</p>
<h3>Model Conversion Pipeline</h3>
<p>The code in this subchapter is located in the <code>dnn_model_runner</code> module and can be executed with the line:</p>
<p>`` python -m dnn_model_runner.dnn_conversion.pytorch.segmentation.py_to_py_fcnresnet50 ``</p>
<p>The following code contains the description of the below-listed steps:</p><ol type="1">
<li>instantiate PyTorch model</li>
<li>convert PyTorch model into <code>.onnx</code></li>
<li>read the transferred network with OpenCV API</li>
<li>prepare input data</li>
<li>provide inference</li>
<li>get colored masks from predictions</li>
<li>visualize results</li>
</ol>
<div class="fragment"><div class="line"># initialize PyTorch FCN ResNet-50 model</div><div class="line">original_model = models.segmentation.fcn_resnet50(pretrained=True)</div><div class="line"></div><div class="line"># get the path to the converted into ONNX PyTorch model</div><div class="line">full_model_path = get_pytorch_onnx_model(original_model)</div><div class="line"></div><div class="line"># read converted .onnx model with OpenCV API</div><div class="line">opencv_net = cv2.dnn.readNetFromONNX(full_model_path)</div><div class="line">print(&quot;OpenCV model was successfully read. Layer IDs: \n&quot;, opencv_net.getLayerNames())</div><div class="line"></div><div class="line"># get preprocessed image</div><div class="line">img, input_img = get_processed_imgs(&quot;test_data/sem_segm/2007_000033.jpg&quot;)</div><div class="line"></div><div class="line"># obtain OpenCV DNN predictions</div><div class="line">opencv_prediction = get_opencv_dnn_prediction(opencv_net, input_img)</div><div class="line"></div><div class="line"># obtain original PyTorch ResNet50 predictions</div><div class="line">pytorch_prediction = get_pytorch_dnn_prediction(original_model, input_img)</div><div class="line"></div><div class="line">pascal_voc_classes, pascal_voc_colors = read_colors_info(&quot;test_data/sem_segm/pascal-classes.txt&quot;)</div><div class="line"></div><div class="line"># obtain colored segmentation masks</div><div class="line">opencv_colored_mask = get_colored_mask(img.shape, opencv_prediction, pascal_voc_colors)</div><div class="line">pytorch_colored_mask = get_colored_mask(img.shape, pytorch_prediction, pascal_voc_colors)</div><div class="line"></div><div class="line"># obtain palette of PASCAL VOC colors</div><div class="line">color_legend = get_legend(pascal_voc_classes, pascal_voc_colors)</div><div class="line"></div><div class="line">cv2.imshow(&#39;PyTorch Colored Mask&#39;, pytorch_colored_mask)</div><div class="line">cv2.imshow(&#39;OpenCV DNN Colored Mask&#39;, opencv_colored_mask)</div><div class="line">cv2.imshow(&#39;Color Legend&#39;, color_legend)</div><div class="line"></div><div class="line">cv2.waitKey(0)</div></div><!-- fragment --><p>To provide the model inference we will use the below picture from the <a href="http://host.robots.ox.ac.uk/pascal/VOC/">PASCAL VOC</a> validation dataset:</p>
<div class="image">
<img src="../../images/2007_000033.jpg" alt="PASCAL VOC img"/>
</div>
<p>The target segmented result is:</p>
<div class="image">
<img src="../../images/2007_000033.png" alt="PASCAL VOC ground truth"/>
</div>
<p>For the PASCAL VOC colors decoding and its mapping with the predicted masks, we also need <code>pascal-classes.txt</code> file, which contains the full list of the PASCAL VOC classes and corresponding colors.</p>
<p>Let's go deeper into each code step by the example of pretrained PyTorch FCN ResNet-50:</p><ul>
<li>instantiate PyTorch FCN ResNet-50 model:</li>
</ul>
<div class="fragment"><div class="line"># initialize PyTorch FCN ResNet-50 model</div><div class="line">original_model = models.segmentation.fcn_resnet50(pretrained=True)</div></div><!-- fragment --><ul>
<li>convert PyTorch model into ONNX format:</li>
</ul>
<div class="fragment"><div class="line"># define the directory for further converted model save</div><div class="line">onnx_model_path = &quot;models&quot;</div><div class="line"># define the name of further converted model</div><div class="line">onnx_model_name = &quot;fcnresnet50.onnx&quot;</div><div class="line"></div><div class="line"># create directory for further converted model</div><div class="line">os.makedirs(onnx_model_path, exist_ok=True)</div><div class="line"></div><div class="line"># get full path to the converted model</div><div class="line">full_model_path = os.path.join(onnx_model_path, onnx_model_name)</div><div class="line"></div><div class="line"># generate model input to build the graph</div><div class="line">generated_input = Variable(</div><div class="line">    torch.randn(1, 3, 500, 500)</div><div class="line">)</div><div class="line"></div><div class="line"># model export into ONNX format</div><div class="line">torch.onnx.export(</div><div class="line">    original_model,</div><div class="line">    generated_input,</div><div class="line">    full_model_path,</div><div class="line">    verbose=True,</div><div class="line">    input_names=[&quot;input&quot;],</div><div class="line">    output_names=[&quot;output&quot;],</div><div class="line">    opset_version=11</div><div class="line">)</div></div><!-- fragment --><p>The code from this step does not differ from the classification conversion case. Thus, after the successful execution of the above code, we will get <code>models/fcnresnet50.onnx</code>.</p>
<ul>
<li>read the transferred network with <a class="el" href="../../d6/d0f/group__dnn.html#ga7faea56041d10c71dbbd6746ca854197" title="Reads a network model ONNX. ">cv.dnn.readNetFromONNX</a> passing the obtained in the previous step ONNX model into it:</li>
</ul>
<div class="fragment"><div class="line"># read converted .onnx model with OpenCV API</div><div class="line">opencv_net = cv2.dnn.readNetFromONNX(full_model_path)</div></div><!-- fragment --><ul>
<li>prepare input data:</li>
</ul>
<div class="fragment"><div class="line"># read the image</div><div class="line">input_img = cv2.imread(img_path, cv2.IMREAD_COLOR)</div><div class="line">input_img = input_img.astype(np.float32)</div><div class="line"></div><div class="line"># target image sizes</div><div class="line">img_height = input_img.shape[0]</div><div class="line">img_width = input_img.shape[1]</div><div class="line"></div><div class="line"># define preprocess parameters</div><div class="line">mean = np.array([0.485, 0.456, 0.406]) * 255.0</div><div class="line">scale = 1 / 255.0</div><div class="line">std = [0.229, 0.224, 0.225]</div><div class="line"></div><div class="line"># prepare input blob to fit the model input:</div><div class="line"># 1. subtract mean</div><div class="line"># 2. scale to set pixel values from 0 to 1</div><div class="line">input_blob = cv2.dnn.blobFromImage(</div><div class="line">    image=input_img,</div><div class="line">    scalefactor=scale,</div><div class="line">    size=(img_width, img_height),  # img target size</div><div class="line">    mean=mean,</div><div class="line">    swapRB=True,  # BGR -&gt; RGB</div><div class="line">    crop=False  # center crop</div><div class="line">)</div><div class="line"># 3. divide by std</div><div class="line">input_blob[0] /= np.asarray(std, dtype=np.float32).reshape(3, 1, 1)</div></div><!-- fragment --><p>In this step we read the image and prepare model input with cv2.dnn.blobFromImage function, which returns 4-dimensional blob. It should be noted that firstly in <code>cv2.dnn.blobFromImage</code> mean value is subtracted and only then pixel values are scaled. Thus, <code>mean</code> is multiplied by <code>255.0</code> to reproduce the original image preprocessing order:</p>
<div class="fragment"><div class="line">img /= 255.0</div><div class="line">img -= [0.485, 0.456, 0.406]</div><div class="line">img /= [0.229, 0.224, 0.225]</div></div><!-- fragment --><ul>
<li>OpenCV <code>cv.dnn_Net</code> inference:</li>
</ul>
<div class="fragment"><div class="line"># set OpenCV DNN input</div><div class="line">opencv_net.setInput(preproc_img)</div><div class="line"></div><div class="line"># OpenCV DNN inference</div><div class="line">out = opencv_net.forward()</div><div class="line">print(&quot;OpenCV DNN segmentation prediction: \n&quot;)</div><div class="line">print(&quot;* shape: &quot;, out.shape)</div><div class="line"></div><div class="line"># get IDs of predicted classes</div><div class="line">out_predictions = np.argmax(out[0], axis=0)</div></div><!-- fragment --><p>After the above code execution we will get the following output:</p>
<div class="fragment"><div class="line">OpenCV DNN segmentation prediction:</div><div class="line">* shape:  (1, 21, 500, 500)</div></div><!-- fragment --><p>Each prediction channel out of 21, where 21 represents the number of PASCAL VOC classes, contains probabilities, which indicate how likely the pixel corresponds to the PASCAL VOC class.</p>
<ul>
<li>PyTorch FCN ResNet-50 model inference:</li>
</ul>
<div class="fragment"><div class="line">original_net.eval()</div><div class="line">preproc_img = torch.FloatTensor(preproc_img)</div><div class="line"></div><div class="line">with torch.no_grad():</div><div class="line">    # obtaining unnormalized probabilities for each class</div><div class="line">    out = original_net(preproc_img)[&#39;out&#39;]</div><div class="line"></div><div class="line">print(&quot;\nPyTorch segmentation model prediction: \n&quot;)</div><div class="line">print(&quot;* shape: &quot;, out.shape)</div><div class="line"></div><div class="line"># get IDs of predicted classes</div><div class="line">out_predictions = out[0].argmax(dim=0)</div></div><!-- fragment --><p>After the above code launching we will get the following output:</p>
<div class="fragment"><div class="line">PyTorch segmentation model prediction:</div><div class="line">* shape:  torch.Size([1, 21, 366, 500])</div></div><!-- fragment --><p>PyTorch prediction also contains probabilities corresponding to each class prediction.</p>
<ul>
<li>get colored masks from predictions:</li>
</ul>
<div class="fragment"><div class="line"># convert mask values into PASCAL VOC colors</div><div class="line">processed_mask = np.stack([colors[color_id] for color_id in segm_mask.flatten()])</div><div class="line"></div><div class="line"># reshape mask into 3-channel image</div><div class="line">processed_mask = processed_mask.reshape(mask_height, mask_width, 3)</div><div class="line">processed_mask = cv2.resize(processed_mask, (img_width, img_height), interpolation=cv2.INTER_NEAREST).astype(</div><div class="line">    np.uint8)</div><div class="line"></div><div class="line"># convert colored mask from BGR to RGB for compatibility with PASCAL VOC colors</div><div class="line">processed_mask = cv2.cvtColor(processed_mask, cv2.COLOR_BGR2RGB)</div></div><!-- fragment --><p>In this step we map the probabilities from segmentation masks with appropriate colors of the predicted classes. Let's have a look at the results:</p>
<div class="image">
<img src="../../images/legend_opencv_color_mask.png" alt="OpenCV Colored Mask"/>
</div>
<p>For the extended evaluation of the models, we can use <code>py_to_py_segm</code> script of the <code>dnn_model_runner</code> module. This module part will be described in the next subchapter.</p>
<h3>Evaluation of the Models</h3>
<p>The proposed in <code>dnn/samples</code> <code>dnn_model_runner</code> module allows to run the full evaluation pipeline on the PASCAL VOC dataset and test execution for the following PyTorch segmentation models:</p><ul>
<li>FCN ResNet-50</li>
<li>FCN ResNet-101</li>
</ul>
<p>This list can be also extended with further appropriate evaluation pipeline configuration.</p>
<h4>Evaluation Mode</h4>
<p>The below line represents running of the module in the evaluation mode:</p>
<div class="fragment"><div class="line">python -m dnn_model_runner.dnn_conversion.pytorch.segmentation.py_to_py_segm --model_name &lt;pytorch_segm_model_name&gt;</div></div><!-- fragment --><p>Chosen from the list segmentation model will be read into OpenCV <code>cv.dnn_Net</code> object. Evaluation results of PyTorch and OpenCV models (pixel accuracy, mean IoU, inference time) will be written into the log file. Inference time values will be also depicted in a chart to generalize the obtained model information.</p>
<p>Necessary evaluation configurations are defined in the <a href="https://github.com/opencv/opencv/tree/master/samples/dnn/dnn_model_runner/dnn_conversion/common/test/configs/test_config.py"><code>test_config.py</code></a>:</p>
<div class="fragment"><div class="line">@dataclass</div><div class="line">class TestSegmConfig:</div><div class="line">    frame_size: int = 500</div><div class="line">    img_root_dir: str = &quot;./VOC2012&quot;</div><div class="line">    img_dir: str = os.path.join(img_root_dir, &quot;JPEGImages/&quot;)</div><div class="line">    img_segm_gt_dir: str = os.path.join(img_root_dir, &quot;SegmentationClass/&quot;)</div><div class="line">    # reduced val: https://github.com/shelhamer/fcn.berkeleyvision.org/blob/master/data/pascal/seg11valid.txt</div><div class="line">    segm_val_file: str = os.path.join(img_root_dir, &quot;ImageSets/Segmentation/seg11valid.txt&quot;)</div><div class="line">    colour_file_cls: str = os.path.join(img_root_dir, &quot;ImageSets/Segmentation/pascal-classes.txt&quot;)</div></div><!-- fragment --><p>These values can be modified in accordance with chosen model pipeline.</p>
<p>To initiate the evaluation of the PyTorch FCN ResNet-50, run the following line:</p>
<div class="fragment"><div class="line">python -m dnn_model_runner.dnn_conversion.pytorch.segmentation.py_to_py_segm --model_name fcnresnet50</div></div><!-- fragment --><h4>Test Mode</h4>
<p>The below line represents running of the module in the test mode, which provides the steps for the model inference:</p>
<div class="fragment"><div class="line">python -m dnn_model_runner.dnn_conversion.pytorch.segmentation.py_to_py_segm --model_name &lt;pytorch_segm_model_name&gt; --test True --default_img_preprocess &lt;True/False&gt; --evaluate False</div></div><!-- fragment --><p>Here <code>default_img_preprocess</code> key defines whether you'd like to parametrize the model test process with some particular values or use the default values, for example, <code>scale</code>, <code>mean</code> or <code>std</code>.</p>
<p>Test configuration is represented in <a href="https://github.com/opencv/opencv/tree/master/samples/dnn/dnn_model_runner/dnn_conversion/common/test/configs/test_config.py"><code>test_config.py</code></a> <code>TestSegmModuleConfig</code> class:</p>
<div class="fragment"><div class="line">@dataclass</div><div class="line">class TestSegmModuleConfig:</div><div class="line">    segm_test_data_dir: str = &quot;test_data/sem_segm&quot;</div><div class="line">    test_module_name: str = &quot;segmentation&quot;</div><div class="line">    test_module_path: str = &quot;segmentation.py&quot;</div><div class="line">    input_img: str = os.path.join(segm_test_data_dir, &quot;2007_000033.jpg&quot;)</div><div class="line">    model: str = &quot;&quot;</div><div class="line"></div><div class="line">    frame_height: str = str(TestSegmConfig.frame_size)</div><div class="line">    frame_width: str = str(TestSegmConfig.frame_size)</div><div class="line">    scale: float = 1.0</div><div class="line">    mean: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])</div><div class="line">    std: List[float] = field(default_factory=list)</div><div class="line">    crop: bool = False</div><div class="line">    rgb: bool = True</div><div class="line">    classes: str = os.path.join(segm_test_data_dir, &quot;pascal-classes.txt&quot;)</div></div><!-- fragment --><p>The default image preprocessing options are defined in <code>default_preprocess_config.py</code>:</p>
<div class="fragment"><div class="line">pytorch_segm_input_blob = {</div><div class="line">    &quot;mean&quot;: [&quot;123.675&quot;, &quot;116.28&quot;, &quot;103.53&quot;],</div><div class="line">    &quot;scale&quot;: str(1 / 255.0),</div><div class="line">    &quot;std&quot;: [&quot;0.229&quot;, &quot;0.224&quot;, &quot;0.225&quot;],</div><div class="line">    &quot;crop&quot;: &quot;False&quot;,</div><div class="line">    &quot;rgb&quot;: &quot;True&quot;</div><div class="line">}</div></div><!-- fragment --><p>The basis of the model testing is represented in <code>samples/dnn/segmentation.py</code>. <code>segmentation.py</code> can be executed autonomously with provided converted model in <code>--input</code> and populated parameters for <code>cv2.dnn.blobFromImage</code>.</p>
<p>To reproduce from scratch the described in "Model Conversion Pipeline" OpenCV steps with <code>dnn_model_runner</code> execute the below line:</p>
<div class="fragment"><div class="line">python -m dnn_model_runner.dnn_conversion.pytorch.segmentation.py_to_py_segm --model_name fcnresnet50 --test True --default_img_preprocess True --evaluate False</div></div><!-- fragment --> </div></div><!-- contents -->
<!-- HTML footer for doxygen 1.8.6-->
<!-- start footer part -->
<hr class="footer"/><address class="footer"><small>
Generated on Fri Apr 2 2021 11:36:34 for OpenCV by &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="../../doxygen.png" alt="doxygen"/>
</a> 1.8.13
</small></address>
<script type="text/javascript">
//<![CDATA[
addTutorialsButtons();
//]]>
</script>
</body>
</html>
